/*
 * @autor Carlos Padoa
 */
package br.com.felix.fwt.servlet;

import java.io.File;
import java.io.IOException;
import java.util.Locale;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.log4j.Logger;

import br.com.felix.fwt.log.LoggerFactory;
import br.com.felix.fwt.security.Profile;
import br.com.felix.fwt.servlet.exception.FormNotFoundException;
import br.com.felix.fwt.ui.Form;

/**
 * The Class FelixWebToolkitServlet is the Front Controller of the
 * framework. All of the website's requests are mapped to this Servlet.
 * It delegates its mode of operation to the FrontController interface.
 */
public abstract class FelixWebToolkitServlet extends HttpServlet {
	

	private Logger logger = null;
	
	/**
	 * The Constant serialVersionUID required by the serializable interface.
	 */
	private static final long serialVersionUID = -576331252796570862L;
	
	/**
	 * The webpackage attribute is used to simplify the URLs requested by
	 * the user, as the framework uses the URL to know which class should be
	 * called.
	 */
	private static String webpackage;
	
	/**
	 * A lista of supported locales.
	 * */
	private static String[] supportedLocales;
	
	/**
	 * The thread local fwt context is used to give access to the
	 * HttpServletRequest, HttpServletResponse and Form (the form that is
	 * serving the current request) objects. This is done by the method
	 * getFwtContext(). This is necessary, for example, to allow the
	 * implementation of the ServerSideInclude class. The ServerSideInclude
	 * class uses the Http objects to perform its work. It is very useful to
	 * give access to the current processing form, allowing it to be
	 * referenced globally by any framework object. One example of use is
	 * the declaration of CSS classes, or declaration of Javascript
	 * functions.
	 */
	private static ThreadLocal<FwtContext> threadLocalFwtContext = new ThreadLocal<FwtContext>();
	
	/**
	 * The {@link FrontController} that tells if framework will behave stateful or stateless.
	 * Must be set on concrete subclass. 
	 * */
	private FrontController frontController;

	/**
	 * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest,
	 *      javax.servlet.http.HttpServletResponse)
	 */
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		doPost(req, resp);
	}
	


	/**
	 * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest,
	 *      javax.servlet.http.HttpServletResponse)
	 */
	@SuppressWarnings("unchecked")
	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException {
		Locale locale = getLocale(request, resp);
				
		FwtContext threadLocalContext = new FwtContext(request, resp, locale);
		threadLocalFwtContext.set(threadLocalContext);
		String requestedForm = webpackage + "." + getRequestedForm(request);
		
		try {
			frontController.processRequestedForm(requestedForm, this, request, resp);
		}
		catch (FormNotFoundException e) {
			logger.error("Form not found.", e);
			resp.sendError(404);
		}
	}


	protected Locale getLocale(HttpServletRequest request, HttpServletResponse resp) {
		Locale locale = null;
		HttpSession session = request.getSession();
		
		if (supportedLocales != null){
			String pathInfo = request.getPathInfo();
			if (pathInfo.endsWith("/")){
				pathInfo = pathInfo.substring(0, pathInfo.length()-1);
			}
			if (pathInfo.length()>=5){
				String l = pathInfo.substring(pathInfo.length()-5);
				for(String localeName : supportedLocales){
					if (localeName.equals(l)){
						String[]localeParts = localeName.split("-");
						locale = new Locale(localeParts[0], localeParts[1]);
						session.setAttribute("locale", locale);
						break;
					}
				}
			}
		}
		if (locale == null){
			locale = (Locale) session.getAttribute("locale");
			if (locale == null){
				locale = new Locale("pt", "BR");
				session.setAttribute("locale", locale);
			}
		}
		return locale;
	}



	/**
	 * Returns the complete class name that will process the request.
	 * */
	protected String getRequestedForm(HttpServletRequest request) {
		String pathInfo = request.getPathInfo();
		if (pathInfo.endsWith("/"))pathInfo = pathInfo.substring(0, pathInfo.length() - 1);
		if (pathInfo.startsWith("/"))pathInfo = pathInfo.substring(1);
		int index = pathInfo.indexOf('/');
		String requestedForm = index > 0 ? pathInfo.substring(0, index) : pathInfo;
		return requestedForm;
	}
	


	/**
	 * Gets the FwtContext of the current processing request.
	 * 
	 * @return the FwtContext.
	 */
	public static FwtContext getFwtContext() {
		return threadLocalFwtContext.get();
	}
	

	/**
	 * Initializes log4j configuration.
	 * Initializes front controller.
	 * Gets webpackage initial parameter.
	 * 
	 * @see HttpServlet#init()
	 * */
	@Override
	public void init(ServletConfig config) throws ServletException {
		super.init(config);
		
		File log4JConfigurationFile = getLog4JConfigurationFile();
		LoggerFactory.setLog4JPath(log4JConfigurationFile);
		logger = LoggerFactory.getLogger(FelixWebToolkitServlet.class);
		if(log4JConfigurationFile!=null){
			logger.info("Log4J configured to:"+log4JConfigurationFile.getAbsolutePath());
		}
		else{
			logger.info("Using CONSOLE logging ONLY!");
		}
		
		this.frontController = getFrontController();
		if (frontController == null) {
			String msg = "Front controller is NOT properly configured! It is null!";
			logger.fatal(msg);
			throw new ServletException(msg);			
		}
		logger.info(String.format("Front controller configured to: '%s'.", frontController.getClass().getName()));
		

		webpackage = config.getInitParameter("web-package");
		logger.info("Webpackage configured to: '" + webpackage + "'.");
		
		String supportedLocales = config.getInitParameter("supported-locales");
		if (supportedLocales!=null){
			this.supportedLocales = supportedLocales.split(",");
		}
	}
	
	

	/**
	 * Returns the String used in the hidden field 'action'.
	 * */
	public static String formAction(Class<?> clazz) {
		return clazz.getSimpleName();
	}
	
	
	/**
	 * Returns a form previously loaded. Null otherwise.
	 * Makes no sense with stateless front controller. 
	 * */
	public static <T extends Form> T getLoadedForm(Class<T> clazz){
		FwtContext ctx = getFwtContext();
		HttpServletRequest req = ctx.request;
		HttpSession session = req.getSession();
		return (T)session.getAttribute(clazz.getCanonicalName());
	}
	
	
	/**
	 * Obtains the user profile.
	 * */
	public static Profile getUserProfile(){
		FwtContext ctx = getFwtContext();
		if(ctx!=null && ctx.request!=null){
			HttpSession session = ctx.request.getSession();
			Object objProfile = session.getAttribute("USER_PROFILE");
			if(objProfile!=null)return (Profile)objProfile;
		}
		return null;
	}
	
	
	/**
	 * Sets the user profile in the Context and in the user Session.
	 * */
	public static void setUserProfile(Profile profile){
		FwtContext ctx = getFwtContext();
		if(ctx.request!=null){
			HttpSession session = ctx.request.getSession();
			session.setAttribute("USER_PROFILE", profile);
		}
	}
	
	
	
	/**
	 * Sets the user profile in the Context and in the user Session.
	 * */
	public static void setUserLocale(Locale locale){
		FwtContext ctx = getFwtContext();
		ctx.locale = locale;
	}
	

	/**
	 * Sets the FwtContext (thread local) if it is not set yet. This is intended to be used for testing purposes.
	 * */
	public static final void setFwtContext(FwtContext ctx){
	    if(threadLocalFwtContext.get()==null){
	        threadLocalFwtContext.set(ctx);
	    }
	}
	
	
	/**
	 * Tells where to find log4j configuration.
	 * */
	protected abstract File getLog4JConfigurationFile();
	
	/**
	 * Tells how the servlet will behave.
	 * */
	protected abstract FrontController getFrontController();
	
	
	/**
	 * Signals that the form's contents are not valid anymore.
	 * And that the form should be reloaded on the next request.
	 * Makes no sense with stateless front controller.
	 * */
	public static void invalidateForm(Form form){
		HttpServletRequest req = getFwtContext().request;
		HttpSession session = req.getSession();
		session.removeAttribute(form.getClass().getCanonicalName());
	}


	/**
	 * Gets the initial parameter that tells which package has the Form classes.
	 * */
	protected static String getWebpackage() {
		return webpackage;
	}
}
